﻿// Partial Copyright (c) LogoUI Software Solutions LTD
// Autor: Vladislav Spivak
// This source file is the part of LogoFX Framework http://logofx.codeplex.com
// See accompanying licences and credits.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Linq;
using System.Reflection;
using Autofac;
using Autofac.Core;
using Autofac.Integration.Mef;
using IContainer = Autofac.IContainer;

//this class is a reciepe

namespace Caliburn.Micro
{
    /// <summary>
    /// <c>Bootstrapper</c> with MEF as module loader and <c>Autofac</c> as <c>IoC</c> container
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public  class AutoMefBootstrapper<T> : BootstrapperBase<T>
    {

        #region Fields
        private CompositionContainer _mefContainer;
        //private ILifetimeScope _defaultLifetimeScope;
        private readonly ILog _logger = LogManager.GetLog(typeof(AutoMefBootstrapper<T>));
        private readonly ContainerBuilder _containerBuilder = new ContainerBuilder();
        private IContainer _container;
        #endregion

        /// <summary>
        /// Gets the <c>Autofac</c> container.
        /// </summary>
        /// <value>The container.</value>
        public IContainer Container
        {
            get { return _container; }
        }

        /// <summary>
        /// Returns tenant Id
        /// </summary>
        /// <returns>id</returns>
        protected virtual object GetTenantId()
        {
            return "default";
        }

        /// <summary>
        /// Override to configure the framework and setup your <c>IoC</c> container.
        /// </summary>
        protected override void Configure()
        {
           base.Configure();
            _mefContainer = new CompositionContainer(                
                            new AggregateCatalog(
                            AssemblySource.Instance
                            .Select(x => new AssemblyCatalog(x))
                            .OfType<System.ComponentModel.Composition.Primitives.ComposablePartCatalog>()));

            ContainerBuilder cb = _containerBuilder;

            cb.RegisterComposablePartCatalog(_mefContainer.Catalog);

            //  register the single window manager for this container
            cb.Register<IWindowManager>(c => new WindowManager()).InstancePerLifetimeScope();
            //  register the single event aggregator for this container
            cb.Register<IEventAggregator>(c => new EventAggregator()).InstancePerLifetimeScope();
            // self
            cb.RegisterInstance(_containerBuilder);

            cb.RegisterType<T>().AsSelf().InstancePerLifetimeScope();

            //register modules if available
            RegisterModules();

            //  register view models if needed
            RegisterViewsAndViewModels();

            OnConfigure(cb);

            //_container =  cb.Build();
            _container = cb.Build();
        }

        /// <summary>
        /// Called when configure almost complete.
        /// </summary>
        /// <param name="cb">The container builder.</param>
        protected virtual void OnConfigure(ContainerBuilder cb)
        {        
        }

        ///// <summary>
        ///// Gets the current lifetime scope.
        ///// </summary>
        ///// <value>The current lifetime scope.</value>
        //public ILifetimeScope CurrentLifetimeScope
        //{
        //    get { return _defaultLifetimeScope??(_defaultLifetimeScope = Container.BeginLifetimeScope());} 
        //}

        /// <summary>
        /// Override this to provide an <c>IoC</c> specific implementation.
        /// </summary>
        /// <param name="serviceType">The service to locate.</param>
        /// <param name="key">The key to locate.</param>
        /// <returns>The located service.</returns>
        protected override object GetInstance(Type serviceType, string key)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                if (_container.IsRegistered(serviceType))
                    return _container.Resolve(serviceType);
            }
            else
            {
                if (_container.IsRegisteredWithName(key, serviceType))
                    return _container.ResolveNamed(key, serviceType);
            }
            throw new Exception(string.Format("Could not locate any instances of contract {0}.", key ?? serviceType.Name));
        }

        /// <summary>
        /// Override this to provide an <c>IoC</c> specific implementation
        /// </summary>
        /// <param name="serviceType">The service to locate.</param>
        /// <returns>The located services.</returns>
        protected override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return _container.Resolve(typeof(IEnumerable<>).MakeGenericType(serviceType)) as IEnumerable<object>;
        }

        /// <summary>
        /// Override this to provide an <c>IoC</c> specific implementation.
        /// </summary>
        /// <param name="instance">The instance to perform injection on.</param>
        protected override void BuildUp(object instance)
        {
            _container.InjectProperties(instance);
        }

        private void RegisterViewsAndViewModels()
        {
            _containerBuilder.RegisterAssemblyTypes(AssemblySource.Instance.ToArray())
                //  must be a type that ends with ViewModel
                .Where(type => type != typeof(T) && type.Name.EndsWith("ViewModel"))
                //  must be in a namespace ending with ViewModels
                .Where(type => !(string.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace != null && type.Namespace.EndsWith("ViewModels"))
                //  must implement INotifyPropertyChanged (deriving from PropertyChangedBase will statisfy this)
                .Where(type => type.GetInterface(typeof(INotifyPropertyChanged).Name, false) != null)
                //  registered as self
                .AsSelf()
                //  always create a new one
                .InstancePerDependency();

            //  register views
            _containerBuilder.RegisterAssemblyTypes(AssemblySource.Instance.ToArray())
                //  must be a type that ends with View
                .Where(type => type.Name.EndsWith("View"))
                //  must be in a namespace that ends in Views
                .Where(type => !(string.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace != null && type.Namespace.EndsWith("Views"))
                //  registered as self
                .AsSelf()
                //  always create a new one
                .InstancePerDependency();
        }


        /// <summary>
        /// Override to tell the framework where to find assemblies to inspect for views, etc.
        /// </summary>
        /// <returns>A list of assemblies to inspect.</returns>
        protected override IEnumerable<Assembly> SelectAssemblies()
        {
              if (!Directory.Exists(ModulesPath))
                 Directory.CreateDirectory(ModulesPath);
 
               CompositionHost.Initialize(new DirectoryCatalog(ModulesPath));
               CompositionInitializer.SatisfyImports(this);

               IEnumerable<Assembly> local =
                   GetType().Assembly
                   .GetReferencedAssemblies()
                   .Select(Assembly.Load)
                   .Concat(new[] { GetType().Assembly })
                   .Distinct();

               if (Modules != null)
                   return local.Concat(Modules.Select(a => a.GetType().Assembly)).Distinct();

               return local;
        }


        /// <summary>
        /// <see cref="Autofac"/> modules list
        /// </summary>
        [ImportMany(typeof(IModule))]
        public IModule[] Modules { get; set; }

        /// <summary>
        /// Gets the modules path that MEF need to search.
        /// </summary>
        /// <value>The modules path.</value>
        protected virtual string ModulesPath
        {
            get { return "."; }
        }

        private void RegisterModules()
        {
            if (Modules != null)
                Modules.Apply(a => _containerBuilder.RegisterModule(a));
        }
    }

}
